/*
    http://bhepple.freeshell.org

    Copyright (C) 2010 Bob Hepple <bhepple@freeshell.org>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

*/

/**
 * \file tbs.c
 */

#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <tbs.h>

static int tbs_min_maxlen = 1;
static enum { UNKNOWN, NONE, RETURN, ABORT } checking = UNKNOWN;
#define SENTINEL 0xcafebead

size_t sizeof_tbs(size_t maxlen_in)
{
    size_t retval;
    size_t maxlen = maxlen_in;

    if (maxlen < tbs_min_maxlen) maxlen = tbs_min_maxlen;
    /* string area is rounded up to a multiple of sizeof(size_t) for
     * alignment of sentinel2: */
    retval = 1 + maxlen / sizeof(size_t);
    retval += 3; /* sentinel1 & 2 plus maxlen */
    retval *= sizeof(size_t);
    return(retval);
}

static int check_tbs(const tbs s)
{
    int retval = 0;
    size_t *sentinel2;

    if (checking == UNKNOWN) {
        char *p = getenv("TBSTRING_CHECK");
        int val = 0;
        if (p) val = atoi(p);
        switch (val) {
        case 0: checking = NONE; break;
        case 1: checking = RETURN; break;
        case 2: checking = ABORT; break;
        default: checking = NONE;
        }
    }

    if (checking == NONE) goto end;

    if (!s) goto end; /* NULL pointer is untestable */

    if ( (s->sentinel1 != SENTINEL) || (s->maxlen < 0) ) {
        retval = -1;
        goto end;
    }

    sentinel2 = (size_t *) ((void *)s + sizeof_tbs(s->maxlen)) - 1;
    if (*sentinel2 != SENTINEL) {
        retval = -1;
        goto end;
    }
        
end:
    if (retval && checking == ABORT) abort();
    return(retval);
}

void tbs_set_min_maxlen(int maxlen)
{
    
    if (maxlen > 0) tbs_min_maxlen = maxlen;
}

tbs tbs_new_maxlen(int maxlen_in)
{
    tbs retval = NULL;
    int maxlen = maxlen_in;
    size_t physize;

    if (maxlen >= 0) {
        if (maxlen < tbs_min_maxlen) maxlen = tbs_min_maxlen;
        physize = sizeof_tbs(maxlen);
        retval = (tbs) xmalloc(physize);
        if (retval) {
            size_t *sentinel2 = (size_t *) ((void *)retval + physize) - 1;
            retval->sentinel1 = SENTINEL;
            *sentinel2 = SENTINEL;
            retval->str[0] = 0;
            retval->maxlen = maxlen;
        }
    }
    return(retval);
}

tbs tbs_new(const char *s)
{
    tbs retval = NULL;
    int len = 0;

    if (s) {
        len = strlen(s);
    }

    retval = tbs_new_maxlen(len);
    if (retval) {
        if (len) {
            strcpy(retval->str, s);
        }
    }
    return(retval);
}

void tbs_free(tbs *s)
{
    if (s && *s) {
        check_tbs(*s);
        xfree(*s);
        *s = NULL;
    }
}

int tbs_get_maxlen(const tbs s)
{
    int retval = 0;

    if (s) {
        if (check_tbs(s)) {
            retval = -1;
        } else {
            retval = s->maxlen;
        }
    }
    return(retval);
}

int tbs_get_length(const tbs  s)
{
    int retval = 0;

    if (s) {
        if (check_tbs(s)) {
            retval = -1;
        } else {
            retval = strlen(s->str);
        }
    }
    return(retval);
}

char *tbs_get_str(tbs s)
{
    char *retval = NULL;

    if (s) {
        if (check_tbs(s)) {
            retval = NULL;
        } else {
            retval = s->str;
        }
    }
    return(retval);
}

tbs tbs_append_str(tbs *       dest,
                   const char *source)
{
    tbs retval = NULL;

    if ( !(dest && *dest) ) {
        retval = tbs_new(source);
        goto end;
    }
    
    if (check_tbs(*dest)) goto end;

    retval = *dest;
    if (source != NULL && *source != 0) {
        int newlen = strlen(source);
        int oldlen = strlen(retval->str);

        if (newlen + oldlen > retval->maxlen - 1) {
            if ( !(retval = tbs_resize(&retval, oldlen + newlen)) ) goto end;
        }
        strcpy(retval->str + oldlen, source);
    }

end:
    if (retval && dest) *dest = retval;
    return(retval);
}

tbs tbs_set_str(tbs *dest, char *source)
{
    tbs retval = NULL;

    if ( !(dest && *dest) ) {
        retval = tbs_new(source);
        goto end;
    }
    
    if (check_tbs(*dest)) goto end;

    retval = *dest;
    if (source == NULL || *source == 0) {
        retval->str[0] = 0;
    } else {
        int len = strlen(source);
        
        if (len > retval->maxlen) {
            if ( !(retval = tbs_resize(&retval, len)) ) goto end;
        }
        strcpy(retval->str, source);
    }

end:
    if (retval && dest) *dest = retval;
    return(retval);
}

tbs tbs_get_substr(const tbs source, int pos_in, 
                             int num_in)
{
    tbs retval = NULL;
    int len = 0;
    int pos = pos_in;
    int num = num_in;

    if (!source) {
        retval = tbs_new("");
        goto end;
    }

    if (check_tbs(source)) goto end;

    len = strlen(source->str);
    if (pos == -1) pos = (len ? len - 1: 0);
    if (num < 0) {
        num = -num;
        if (num > pos) num = pos;
        pos -= num;
    }
    if (pos < 0) pos = 0;
    if ((pos >= 0) && (pos <= len) && (num >= 0)) {
        if ( (retval = tbs_new_maxlen(num)) ) {
            strncpy(retval->str, source->str + pos, num);
            retval->str[num] = 0;
        }
    }
end:
    return(retval);
}

tbs tbs_set_substr(tbs *dest, int pos_in, 
                   const tbs source)
{
    tbs retval = NULL;
    int len = 0;
    int slen = 0;
    int pos = pos_in;
    int newlen = 0;

    if ( !(dest && *dest) ) {
        retval = tbs_strcpy(NULL, source);
        goto end;
    }

    if (check_tbs(*dest)) goto end;

    retval = *dest;
    
    if ( !(source && source->str[0]) ) goto end;

    if (check_tbs(source)) {
        retval = NULL;
        goto end;
    }

    slen = strlen(source->str);
    len = strlen(retval->str);

    if (pos == -1) pos = len;
    if (pos > len) pos = len;
    if (pos < 0) pos = 0;

    newlen = pos + slen;
    if (newlen > retval->maxlen) {
        if ( !(retval = tbs_resize(&retval, newlen)) ) goto end;
    }
            
    memcpy(retval->str + pos, source->str, slen);
    if (newlen > len) retval->str[newlen] = 0;

end:
    if (retval && dest) *dest = retval;
    return(retval);
}

void tbs_fill(tbs dest, int p)
{
    if (dest) {
        memset(dest->str, p, dest->maxlen);
        dest->str[dest->maxlen] = 0;
    }
}

tbs tbs_resize(tbs *dest, int newlen)
{
    tbs retval = NULL;

    if ( !(dest && *dest) ) {
        retval = tbs_new_maxlen(newlen);
        goto end;
    }

    if (check_tbs(*dest)) goto end;

    if (newlen >= 0) {
        retval = *dest;
        if (newlen > retval->maxlen) {
            size_t physize = sizeof_tbs(newlen);

            retval = xrealloc(retval, physize);
            if (retval) {
                size_t *sentinel2 = (size_t *) ((void *)retval + physize) - 1;
                *sentinel2 = SENTINEL;
                retval->str[newlen] = 0;
                retval->maxlen = newlen;
            }
        }
    }

end:
    if (retval && dest) *dest = retval;
    return(retval);
}

tbs tbs_delchars(tbs *dest, int pos_in, int num)
{
    int len = 0;
    int pos = pos_in;
    tbs retval = NULL;

    if (! (dest && *dest) ) {
        retval = tbs_new("");
        goto end;
    }

    if (check_tbs(*dest)) goto end;

    retval = *dest;
    len = strlen(retval->str);

    if (pos == -1) {
        /* delete last char */
        if (len > 0) {
            pos = len - 1;
        } else {
            pos = 0;
        }
    } else if ( !((pos >= 0) && (pos < len)) ) {
        goto end;
    }

    if (len > 0) {
        if (len <= (pos + num)) {
            retval->str[pos] = 0;
        } else {
            char *d, *s;
            
            d = retval->str + pos;
            s = d + num;
            while ( (*d++ = *s++) );
        }
    }

end:
    if (retval && dest) *dest = retval;
    return(retval);
}

tbs tbs_insert(tbs *dest, int pos_in, 
                         const tbs source)
{
    tbs retval = NULL;
    tbs tmp = NULL;
    int len = 0;
    int slen = 0;
    int pos = pos_in;
    
    if (! (dest && *dest) ) {
        retval = tbs_strcpy(&retval, source);
        goto end;
    }

    if (check_tbs(*dest)) goto end;

    retval = *dest;
    len = strlen(retval->str);
    slen = strlen(source->str);

    if (slen == 0) goto end;

    if (pos == -1) pos = len;
    if (pos > len) pos = len;

    if ((len + slen) > retval->maxlen) {
        retval = tbs_resize(&retval, len + slen);
        if (retval == NULL) goto end;
    }

    if (pos < len) {
        tmp = tbs_new(retval->str + pos);
        if (tmp == NULL) goto end;
        retval->str[pos] = 0;
    }
    tbs_strcat(&retval, source);
    if (tmp) {
        tbs_strcat(&retval, tmp);
        tbs_free(&tmp);
    }

end:
    if (retval && dest) *dest = retval;
    return(retval);
}

int tbs_trunc(tbs dest, int pos)
{
    int retval = 0;

    if (dest) {
        if (check_tbs(dest)) {
            retval = -1;
        } else {
            if ((pos >= 0) && (pos < strlen(dest->str))) dest->str[pos] = 0;
        }
    }
    return(retval);
}

int  tbs_rtrim(tbs dest)
{
    int retval = 0;
    char *p;
    int len;

    if (dest) {
        if (check_tbs(dest)) {
            retval = -1;
        } else {
            len = strlen(dest->str);
            if (len > 0) {
                for (p = dest->str + len - 1; 
                     (p >= dest->str) && isspace(*p); 
                     p--) 
                    *p = 0;
            }
        }
    }
    return(retval);
}

int tbs_ltrim(tbs dest)
{
    int retval = 0;
    char *d, *s;

    if (check_tbs(dest)) {
        retval = -1;
    } else {
        if (dest && dest->str[0]) {
            for (s = dest->str; *s && isspace(*s); s++) ;
            if (s > dest->str) {
                for (d = dest->str; (*d++ = *s++);) 
                    ;
            }
        }
    }
    return(retval);
}

int tbs_trim(tbs dest)
{
    int retval = 0;

    if (check_tbs(dest)) {
        retval = -1;
    } else {
        if ( (retval = tbs_rtrim(dest)) == 0) {
            retval = tbs_ltrim(dest);
        }
    }
    return(retval);
}

int tbs_lastchar(const tbs s)
{
    int retval = 0;

    if (check_tbs(s)) {
        retval = -1;
    } else {
        if (s) {
            retval = s->str[strlen(s->str) - 1];
        }
    }
    return(retval);
}

void tbs_upper(tbs dest)
{
    if (dest) {
        char *p;
        if (!check_tbs(dest)) {
            for (p = dest->str; *p; p++) *p = toupper(*p);
        }
    }
}

void tbs_lower(tbs dest)
{
    if (dest) {
        char *p;
        if (!check_tbs(dest)) {
            for (p = dest->str; *p; p++) *p = tolower(*p);
        }
    }
}

int tbs_strcmp(const tbs s1, const tbs s2)
{
    int retval = 0;

    if (check_tbs(s1) || check_tbs(s2)) {
        retval = -1; /* not really an error! */
    } else {
        if (s1 && !s2) {
            retval = 1;
        } else if (!s1 && s2) {
            retval = -1;
        } else if (s1 && s2) {
            retval = strcmp(s1->str, s2->str);
        }
    }
    return(retval);
}

int tbs_strncmp(const tbs s1, const tbs s2, size_t n)
{
    int retval = 0;
    
    if (check_tbs(s1) || check_tbs(s2)) {
        retval = -1; /* not really an error! */
    } else {
        if (s1 && !s2) {
            retval = 1;
        } else if (!s1 && s2) {
            retval = -1;
        } else if (s1 && s2) {
            retval = strncmp(s1->str, s2->str, n);
        }
    }

    return(retval);
}

int tbs_strcasecmp(const tbs s1, const tbs s2)
{
    int retval = 0;

    if (check_tbs(s1) || check_tbs(s2)) {
        retval = -1; /* not really an error! */
    } else {
        if (s1 && !s2) {
            retval = 1;
        } else if (!s1 && s2) {
            retval = -1;
        } else if (s1 && s2) {
            retval = strcasecmp(s1->str, s2->str);
        }
    }

    return(retval);
}

int tbs_strncasecmp(const tbs s1, const tbs s2, size_t len)
{
    int retval = 0;

    if (check_tbs(s1) || check_tbs(s2)) {
        retval = -1; /* not really an error! */
    } else {
        if (s1 && !s2) {
            retval = 1;
        } else if (!s1 && s2) {
            retval = -1;
        } else if (s1 && s2 && len > 0) {
            retval = strncasecmp(s1->str, s2->str, len);
        }
    }

    return(retval);
}

tbs tbs_strcpy(tbs *dest, const tbs source) {
    tbs retval = NULL;
    int newlen;

    if (check_tbs(source)) goto end;
    if (dest) {
        if (check_tbs(*dest)) goto end;
    }

    if ( !source ) {
        retval = tbs_new("");
        goto end;
    }

    newlen = strlen(source->str);

    if (! (dest && *dest) ) {
        retval = tbs_new_maxlen(newlen);
    } else {
        retval = *dest;
    }

    if (retval->maxlen < newlen) {
        retval = tbs_resize(&retval, newlen);
    }
    if (retval) strcpy(retval->str, source->str);

end:
    if (retval && dest) *dest = retval;
    return(retval);
}

tbs tbs_strcat(tbs *dest, const tbs source_in) {
    tbs retval = NULL;
    int newlen;
    int len;
    int slen;
    tbs source = source_in;

    if (check_tbs(source)) goto end;
    if (dest) {
        if (check_tbs(*dest)) goto end;
    }

    if ( !(dest && *dest) ) {
        retval = tbs_strcpy(&retval, source);
        goto end;
    }
    retval = *dest;

    if ( !(source && source->str[0]) ) {
        goto end;
    }

    len = strlen(retval->str);
    slen = strlen(source->str);
    newlen = len + slen;
    if (retval->maxlen < newlen) {
        retval = tbs_resize(&retval, newlen);
        if (source == *dest) source = retval;
    }
    if (retval) {
        memcpy(retval->str + len, source->str, slen);
        retval->str[newlen] = 0;
    }

end:
    if (retval && dest) *dest = retval;
    return(retval);
}

int tbs_strchr(const tbs s, int c)
{
    int retval = -1;

    if (s) {
        char *p = strchr(s->str, c);
        if (!check_tbs(s)) {
            if (p) retval = (p - s->str);
        }
    }
    return(retval);
}

int tbs_strrchr(const tbs s, int c)
{
    int retval = -1;

    if (s) {
        char *p = strrchr(s->str, c);
        if (!check_tbs(s)) {
            if (p) retval = (p - s->str);
        }
    }
    return(retval);
}

tbs tbs_strdup(const tbs source)
{
    tbs retval = NULL;
    
    if (source) {
        if (!check_tbs(source)) {
            retval = tbs_new_maxlen(source->maxlen);
            if (retval)  {
                strncpy(retval->str, source->str, retval->maxlen);
                retval->str[retval->maxlen] = 0;
            }
        }
    } else {
        retval = tbs_new("");
    }
    return(retval);
}

size_t tbs_strspn(const tbs s, tbs accept)
{
    size_t retval = 0;

    if (!check_tbs(s) && !check_tbs(accept)) {
        if (s && accept) {
            retval = strspn(s->str, accept->str);
        }
    }
    return(retval);
}

size_t tbs_strcspn(const tbs s, tbs reject)
{
    size_t retval = 0;

    if (!check_tbs(s) && !check_tbs(reject)) {
        if (s && reject) {
            retval = strcspn(s->str, reject->str);
        }
    }
    return(retval);
}

tbs tbs_strtok(tbs s, int *pos, const tbs delim)
{
    tbs retval = NULL;
    char *p;

    if (check_tbs(s) || check_tbs(delim)) goto end;

    if ( !(s && s->str[0] && delim && delim->str[0]) ) goto end;

    if ( !(pos && (*pos >= 0) && (*pos < strlen(s->str))) ) goto end;

    if ((retval = tbs_get_substr(s, *pos, s->maxlen)) == NULL) goto end;

    for (p = retval->str; *p; p++, (*pos)++) {
        if (strchr(delim->str, *p)) {
            (*pos)++;
            break;
        }
    }
    *p = 0;
    
end:
    return(retval);
}

int tbs_strstr(tbs s, tbs substr)
{
    int retval = -1;
    char *pos;

    if (!check_tbs(s) && !check_tbs(substr)) {
        if (s && substr) {
            if ( (pos = strstr(s->str, substr->str)) ) {
                retval = pos - s->str;
            }
        }
    }
    return(retval);
}

int tbs_sprintf(tbs *dest, char *format, ...)
{
    int retval = -1;
    int len = 0;
    va_list args;


    if ( !dest ) goto end;

    if (check_tbs(*dest)) goto end;

    va_start(args, format);
    len = vsnprintf(NULL, 0, format, args) + 1;


    if ( !*dest ) {
        if ((*dest = tbs_new_maxlen(len)) == NULL) goto end;
    }

    if ((*dest)->maxlen < len) {
        if (tbs_resize(dest, len) == NULL) goto end;
    }

    va_start(args, format);
    retval = vsnprintf((*dest)->str, len, format, args);
end:
    return(retval);
}
